package agin

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/sessions"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/trace"

	"go_user_manager/pkg/framework"
	"go_user_manager/pkg/utils"
	"go_user_manager/pkg/utils/observability"
	"go_user_manager/pkg/utils/observability/tracing"
	"go_user_manager/pkg/utils/tools"
)

const (
	EventKeyHTTPMethod  = "http.method"
	EventKeyHTTPUrl     = "http.url"
	EventKeyHTTPStatus  = "http.status"
	EventKeyHTTPLatency = "http.latency"
)

var store = sessions.NewCookieStore([]byte("agasdfjaj2Jkasd{{P}"))

// MiddlewareSkip 用于配置不通过中间件的路径;
// middlewarename:pathlist
var MiddlewareSkip = map[string][]string{
	"LogAccess": {"/healthz"},
	"Metrics":   {"/healthz"},
	"Trace":     {"/healthz"},
	"Jwt":       {"/healthz", "/api/v1/open"},
	"SSO":       {"/healthz"},
}

func JWTMiddleware(jwtConfig JWT, logger framework.Logger) gin.HandlerFunc {
	middlewareName := "Jwt"
	return func(c *gin.Context) {
		if tools.SliceContainsPrefix(MiddlewareSkip[middlewareName], c.FullPath()) {
			c.Next()
			return
		}

		// 获取 JWT token
		authHeader := c.GetHeader("Authorization")
		if len(authHeader) == 0 {
			logger.Error("未提供认证信息", errors.New("Invalid authorization header"))
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"code": "authentication_failed",
				"msg":  "未提供认证信息",
			})
			return
		}

		// 移除 "Bearer " 前缀
		tokenString := strings.TrimPrefix(authHeader, "Bearer ")

		// 解析 JWT token
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			// 验证签名算法
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			// 返回用于验证的密钥
			return []byte(jwtConfig.SecretKey), nil
		})

		if err != nil || !token.Valid {
			logger.Error("无效的认证信息", err)
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
				"code": "authentication_failed",
				"msg":  "无效的认证信息",
			})
			return
		}

		// 从 token 中获取用户信息
		if claims, ok := token.Claims.(jwt.MapClaims); ok {
			// 将用户信息存储到上下文中
			c.Set("enterprise_id", claims["enterprise_id"])
			c.Set("tenant_id", claims["enterprise_id"])
			c.Set("is_super_admin", claims["is_super_admin"])
			c.Set("enterprise_name", claims["enterprise_name"])
			c.Set("username", claims["username"])
			c.Set("customer_id", claims["customer_id"])
			c.Set("source", claims["source"])
			c.Set("admin_phone", claims["admin_phone"])
			c.Set("user_account_id", claims["user_account_id"])
			// 可以根据需要添加更多用户信息
		}

		c.Next()
	}
}

func SSOHandlerfunc(logger framework.Logger) gin.HandlerFunc {
	middlewareName := "SSO"
	return func(c *gin.Context) {
		if tools.SliceContains(MiddlewareSkip[middlewareName], c.FullPath()) {
			c.Next()
			return
		}

		ssoToken, err := c.Cookie(utils.SSO_COOKIE_NAME)
		if err != nil {
			// 需要登录
			c.JSON(http.StatusOK, gin.H{
				"code": 2999,
				"msg":  "请登录",
				"data": nil,
			})
			c.Abort()
			return
		}
		client := http.Client{}
		req, err := http.NewRequest(http.MethodPost, utils.SSO_VALID+"?token="+ssoToken, nil)
		if err != nil {
			logger.Error("组装晓多token校验请求失败", err)
			c.Abort()
			return
		}
		resp, err := client.Do(req)
		if err != nil {
			logger.Error("执行晓多token校验请求失败", err)
			c.Abort()
			return
		}
		contents, _ := ioutil.ReadAll(resp.Body)
		js, err := simplejson.NewJson(contents)
		if err != nil {
			logger.Error("解析晓多token验证响应失败", err)
			c.Abort()
			return
		}

		code, err := js.Get("code").Int()
		if err != nil {
			logger.Error("获取晓多token校验code失败 ", err)
			c.Abort()
			return
		}

		msg, _ := js.Get("message").String()

		session := sessions.NewSession(store, utils.SESSION_NAME)

		userID, _ := js.Get("data").Get("user_id").String()
		session.Values["user_id"] = userID
		session.Values["name"], _ = js.Get("data").Get("name").String()
		session.Values["union_id"], _ = js.Get("data").Get("union_id").String()
		session.Values["email"], _ = js.Get("data").Get("email").String()
		session.Values["dept_name"], _ = js.Get("data").Get("department").String()
		depts, _ := js.Get("data").Get("departments").Array()
		deptId := ""
		if len(depts) > 0 {
			deptId = depts[0].(string)
		}
		session.Values["dept_id"] = deptId
		// 获取过期时间
		expireTimeStr, _ := js.Get("expire_at").String()
		if !utils.ValidTimeStampExpire(logger, expireTimeStr) {
			logger.Error("时间戳过期", nil)
			c.JSON(http.StatusOK, gin.H{
				"code": code,
				"msg":  "认证过期 已过期",
				"data": nil,
			})
			c.Abort()
			return
		}
		switch code {
		case 0:
			// 保存session+设置cookie
			if err := session.Save(c.Request, c.Writer); err != nil {
				logger.Error("保存cookie失败", err)
			}

			logger.Info("登录成功ssoHandler：session.Values = %v  err =%v\n", session.Values, err)

			// session values在当前请求的上下文存一下，后续业务逻辑才能拿到
			c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "sessionValues", session.Values))

			// c.Next() // 继续往下调用
		default: // 均错误
			c.JSON(http.StatusOK, gin.H{
				"code": code,
				"msg":  fmt.Sprintf("sso_token错误: %v", msg),
				"data": nil,
			})
			c.Abort()
			return
		}

	}
}

func LogAccessMiddleware(ServiceName string, logger framework.Logger) gin.HandlerFunc {
	middlewareName := "LogAccess"
	return func(c *gin.Context) {
		if tools.SliceContains(MiddlewareSkip[middlewareName], c.FullPath()) {
			c.Next()
			return
		}
		start := time.Now()
		c.Request = c.Request.WithContext(observability.AppendEvents(c.Request.Context(),
			framework.EventKeyServiceName, ServiceName,
			framework.EventKeyServerNode, tools.GetLocalIP(),
			EventKeyHTTPMethod, c.Request.Method,
			EventKeyHTTPUrl, c.Request.URL.String()))
		defer func() {
			event := observability.GetEvent(c.Request.Context())
			logger.Info("Access Log", event.EventToArgList()...)
		}()
		c.Next()
		_ = observability.AppendEvents(c.Request.Context(),
			EventKeyHTTPMethod, c.Request.Method,
			EventKeyHTTPUrl, c.Request.URL.String(),
			EventKeyHTTPStatus, c.Writer.Status(),
			EventKeyHTTPLatency, float64(time.Now().Sub(start).Nanoseconds())/1000000)
	}
}

func MetricsMiddleware() gin.HandlerFunc {
	middlewareName := "Metrics"
	return func(c *gin.Context) {
		if tools.SliceContains(MiddlewareSkip[middlewareName], c.FullPath()) {
			c.Next()
			return
		}
		start := time.Now()
		c.Next()
		end := time.Now()
		latency := float64(end.Sub(start).Nanoseconds()) / 1000000
		observability.AppendEvent(c.Request.Context(), EventKeyHTTPLatency, latency)
		var url, method, status, bizCode interface{}
		bizCode = "0"
		event := observability.GetEvent(c.Request.Context())
		if event == nil ||
			event.GetValue(EventKeyHTTPUrl) == nil ||
			event.GetValue(EventKeyHTTPMethod) == nil ||
			event.GetValue(EventKeyHTTPStatus) == nil {
			url = c.Request.URL.String()
			method = c.Request.Method
			status = strconv.Itoa(c.Writer.Status())
			observability.AppendEvents(c.Request.Context(),
				EventKeyHTTPStatus, status,
				EventKeyHTTPUrl, url,
				EventKeyHTTPMethod, method)
		} else {
			url = event.GetValue(EventKeyHTTPUrl).(string)
			method = event.GetValue(EventKeyHTTPMethod).(string)
			status = strconv.Itoa(event.GetValue(EventKeyHTTPStatus).(int))
		}
		if event.GetValue(framework.EventKeyBizCode) != nil {
			bizCode = strconv.FormatUint(uint64(event.GetValue(framework.EventKeyBizCode).(framework.ErrorType)), 10)
		}
		HTTPEndpointInvokeCounter.WithLabelValues(url.(string), method.(string), status.(string), bizCode.(string)).Inc()
		HTTPEndpointLatency.WithLabelValues(url.(string), method.(string)).Observe(latency)
	}
}

func TraceMiddleware(logger framework.Logger) gin.HandlerFunc {
	middlewareName := "Trace"
	return func(c *gin.Context) {
		if tools.SliceContains(MiddlewareSkip[middlewareName], c.FullPath()) {
			c.Next()
			return
		}
		ctx := tracing.HTTPToContext(logger)(c.Request.Context(), c.Request)
		event := observability.GetEvent(ctx)
		var httpMethod, httpUrl interface{}
		if event == nil || event.GetValue(EventKeyHTTPMethod) == nil || event.GetValue(EventKeyHTTPUrl) == nil {
			httpMethod = c.Request.Method
			httpUrl = c.Request.URL.String()
			observability.AppendEvents(ctx, EventKeyHTTPMethod, httpMethod, EventKeyHTTPUrl, httpUrl)
		} else {
			httpMethod = event.GetValue(EventKeyHTTPMethod)
			httpUrl = event.GetValue(EventKeyHTTPUrl)
		}
		var err error
		opName := fmt.Sprintf("HTTP %s : %s", httpMethod, httpUrl)
		tracer := tracing.GlobalTraceProvider().Tracer(tracing.TracerName, trace.WithInstrumentationVersion(tracing.Version))
		opts := []trace.SpanStartOption{
			trace.WithSpanKind(trace.SpanKindServer),
		}
		ctx, span := tracer.Start(ctx, opName, opts...)
		observability.AppendEvent(ctx, framework.EventKeyTraceID, span.SpanContext().TraceID().String())
		c.Request = c.Request.WithContext(ctx)
		defer span.End()
		defer func() {
			if err != nil {
				// generic error
				span.RecordError(err)
				span.SetStatus(codes.Error, err.Error())
				return
			}
		}()
		c.Next()
		observability.AppendEvents(ctx, EventKeyHTTPStatus, c.Writer.Status())
		if c.Writer.Status() > 399 {
			err = errors.New("http response status error: " + strconv.Itoa(c.Writer.Status()))
		}
	}
}

var HTTPEndpointInvokeCounter = promauto.NewCounterVec(prometheus.CounterOpts{
	Name: "http_request_total",
	Help: "http request total",
}, []string{"uri", "method", "status", "biz_code"})

var HTTPEndpointLatency = promauto.NewSummaryVec(prometheus.SummaryOpts{
	Name: "http_request_latency",
	Help: "the summary of http request latency",
	Objectives: map[float64]float64{
		0.5:  0.05,
		0.9:  0.01,
		0.95: 0.005,
		0.99: 0.001},
}, []string{"uri", "method"})
